package com.jf.net

import android.util.Log
import androidx.lifecycle.LifecycleOwner
import com.jf.net.entity.ResponseError
import com.jf.net.entity.ResponseFailedException
import com.jf.net.entity.ResponseResult
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.launch

/**
 * BaseNetWorkManager
 * @author hao.yin
 * @time 2023/9/7 14:05
 * @description
 */

typealias RequestType = Int

abstract class BaseNetWorkManager : INetworkManager {

    companion object {
        const val LOG_INFO = "request retried too many times!"
        const val LOG_REQUEST_SUCCESS = "request success "
        const val LOG_REQUEST_FAILED = "request failed "
        const val DEFAULT_RETRY_TIME: Long = 5000*2
        const val TAG = "JF_NET"
    }

    open val retryTimeMap = HashMap<RequestType, Int>()
    open lateinit var coroutineScope: CoroutineScope

    override fun setInterceptorForCreator(interceptor: IServiceInterceptor): INetworkManager {
        return this
    }

    override fun setLifeCycleOwner(lifecycleOwner: LifecycleOwner): INetworkManager {
        return this
    }

    override fun setCoroutineScope(coroutineScope: CoroutineScope): INetworkManager {
        this.coroutineScope = coroutineScope
        return this
    }

    /***
     * 发送请求，若存在Exception则进行请求重试
     * @param type 发起请求类型，参考RequestType
     * @param retryLimit 请求失败重试次数（可选，默认为0），值小于等于0则无限制重试，值大于0则进行具体重试次数限制
     * @param doWork 结果回调函数
     * */
    fun <T> Flow<ResponseResult<T>>.sendReqWithRetry(
        type: RequestType,
        retryLimit: Int = 0,
        doWork: ((data: T?) -> Unit)? = null){
        coroutineScope.launch(Dispatchers.IO) {
            this@sendReqWithRetry.retryWhen { cause, attempt ->
                retryTimeMap.containsKey(type).and(
                    retryLimit <= 0 || attempt <= retryLimit
                ).also{
                    if(it){
                        retryTimeMap[type] = attempt.toInt()
                        val retryTime = getDelayTimeByRetryTimes(type)
                        Log.w(TAG, "$type req failed[$cause] will retry " +
                                "$attempt-count in next ${retryTime/1000} s")
                        delay(retryTime)
                    }else if(retryLimit > 0){
                        Log.w(TAG, LOG_INFO)
                    }
                }
            }.catch {
                Log.w(TAG, "$type req failed: $it")
                emit(ResponseResult.failure(ResponseError("506", error = it)))
            }.collect{
                doWork?.invoke(it.getData())
            }
        }
    }

    /***
     * 发送请求，若存在Exception则包装为ResponseError返回请求失败结果
     * @param name 发起请求函数名（可选，用于日志分析）
     * @param doWork 结果回调函数
     * */
    fun <T> Flow<ResponseResult<T>>.sendReq(name :String = "", doWork: ((data: ResponseResult<T>?) -> Unit?)? = null){
        coroutineScope.launch (Dispatchers.IO){
            this@sendReq.catch {
                Log.w(TAG, "$name failed: $it")
                emit(ResponseResult.failure(
                    ResponseError(
                        if(it is ResponseFailedException)  it.code else "506",
                        error = it
                    )
                ))
            }.collect {
                doWork?.invoke(it)
            }
        }
    }

    /**
     * 根据类型计算重试延迟周期,默认周期为DEFAULT_RETRY_TIME=5000ms
     * @param type 请求数据类型
     * @return Long 重试周期(ms)
     * */
    private fun getDelayTimeByRetryTimes(type: RequestType): Long {
        var retryTime = 0
        retryTimeMap[type]?.let {
            retryTime = it
        }
        var delayTime: Long = DEFAULT_RETRY_TIME
        if (retryTime > 50) {
            delayTime = DEFAULT_RETRY_TIME * 6
        } else if (retryTime > 20) {
            delayTime = DEFAULT_RETRY_TIME * 4
        } else if (retryTime > 10) {
            delayTime = DEFAULT_RETRY_TIME * 2
        }
        return delayTime
    }

    override fun onDestroy() {

    }
}